<?php
// +----------------------------------------------------------------------
// | 磁盘服务层
// +----------------------------------------------------------------------
// | 主要服务于磁盘路径配置
// +----------------------------------------------------------------------
// | 错误编码头 203xxx
// +----------------------------------------------------------------------
namespace common\service;

use common\model\DelayOrderModel;
use common\model\DelayOrderTaskModel;
use common\model\EntityModel;
use common\model\NoteEntityRelationModel;
use common\model\OriginalPlanNodeDailyReportModel;
use common\model\OriginalPlanNodeDetailModel;
use common\model\OriginalPlanNodeModel;
use common\model\PlanModel;
use common\model\ProjectModel;
use common\model\ReviewFeedbackEntityModel;
use common\model\ReviewFeedbackModel;
use common\model\StepModel;
use common\model\StepUpstreamModel;
use common\model\TaskModel;
use common\model\TaskRelationModel;
use common\model\TaskRelationTagModel;
use support\SingletonTrait;

class OriginalPlanNodeService
{
    use SingletonTrait;

    /**
     * 删除原计划--重复数据
     */
    public function delOriginalPlanNodeData($info)
    {
        $condition = [];
        $condition['name'] = $info['name'];
        $condition['delivery_time'] = $info['delivery_time'];
        $condition['episode_id'] = $info['episode_id'];
        $condition['step_category_id'] = $info['step_category_id'];
        $condition['link_id'] = $info['link_id'];
        $condition['all_task_ids'] = $info['all_task_ids'];
        $condition['project_id'] = $info['project_id'];

        $orgModel = model(OriginalPlanNodeModel::class);
        $num = $orgModel->where($condition)->count();
        if ($num > 1) {
            $orgModel->where(['id' => $info['id']])->delete();
        }
    }


    /**
     * 同步排期原计划节点--任务数据
     * @param int $linkId 排期ID
     * @param int $id 节点ID
     * @return int|void
     */
    public function fixOriginalPlanNodeData(int $linkId, int $id)
    {
        $orgModel = model(OriginalPlanNodeModel::class);

        $info = model(PlanModel::class)
            ->where(['id' => $linkId, 'scope' => PlanModel::SCOPE_SINGLE])
            ->find();
        if (empty($info)) {
            return 0;
        }
        //查询 --- 所有实体、任务
        [$taskIds, $entityIds] = $this->getPlanAllTask($info);
        //同步所有任务 -- 和实体
        $modifyData = [];
        $modifyData['all_entity_ids'] = implode(',', $entityIds);
        $modifyData['all_task_ids'] = implode(',', $taskIds);

        $modifyData['id'] = $id;
        $orgModel->modifyItem($modifyData);
    }

    /**
     * 排期 -- 获取所有任务、实体
     * @param array $info
     * @return array
     */
    public function getPlanAllTask(array $info)
    {
        //组内排期---关联资产
        $relatedWhere = [];
        if ($info['related_entity']) {
            $relatedEntity = explode(',', $info['related_entity']);
            $relatedWhere['task.source_entity_id'] = ['in', $relatedEntity];
        }

        //标准排期、标准审核、生产任务  --- 不是工序任务
        if (in_array($info['type'], [PlanModel::TYPE_REVIEW, PlanModel::TYPE_MEETING, PlanModel::TYPE_TO_DO])) {
            return $this->getStandardPlanTask($info);
        }

        //查询剧集
        $episodeCode = model(EntityModel::class)
            ->where(['id' => $info['entity_id'], 'module_id' => module_by_code('episode')['id']])
            ->value('name');

        //存在-实体属性
        if ($info['attribute']) {
            $relatedWhere['_string'] = "JSON_UNQUOTE(JSON_EXTRACT(entity.json, CONCAT('$.',module.code,'_category'))) = '{$info['attribute']}'";
        }
        //查询 ---查询剧集  环节下 所有任务 实体
        $allTaskEntity = model(TaskModel::class)
            ->join('entity on task.entity_id = entity.id')
            ->join('module on module.id = entity.module_id')
            ->where($relatedWhere)
            ->where([
                'entity.initial_episode_code' => $episodeCode,
                'task.step_category_id' => $info['step_category_id'] ?? 0,
                'entity.project_id' => $info['project_id'] ?? 0,
            ])
            ->where(['task.step_type' => TaskModel::STEP_TYPE_STEP])
            ->field('task.entity_id, task.id')
            ->select();
        //返回任务ID  和 实体ID
        $taskIds = array_column($allTaskEntity, 'id');
        $entityIds = array_unique(array_column($allTaskEntity, 'entity_id'));

        return [$taskIds, $entityIds];
    }

    /**
     * 标准任务--下所有任务
     * @param $info
     * @return array
     */
    public function getStandardPlanTask($info)
    {
        //查询任务--清单
        $taskRelationList = model(TaskRelationModel::class)
            ->setSuffix('_project_' . $info['project_id'])
            ->where(['source_task_id' => $info['task_id'], 'relation_type' => TaskRelationModel::RELATION_TYPE_TEMPLATE_TODO])
            ->field('target_task_id')
            ->select();
        $taskIds = array_column($taskRelationList, 'target_task_id');
        $taskIds[] = $info['task_id'];

        $allEntityIds = $allTaskIds = [];
        if ($taskIds) {
            //返回实体ID｜任务ID---过滤模版待办--主任务
            $taskList = model(TaskModel::class)
                ->where(['id' => ['in', $taskIds], 'step_type' => ['NEQ', TaskModel::STEP_TYPE_TEMPLATE_TO_DO]])
                ->field('id,entity_id')
                ->select();
            $allEntityIds = array_column($taskList, 'entity_id');
            $allTaskIds = array_column($taskList, 'id');
        }

        return [$allTaskIds, $allEntityIds];
    }

    /**
     * 获取阶段ID
     * @param int $taskId 任务ID
     * @return int|mixed
     */
    private function getTaskStageId(int $taskId)
    {
        if (empty($taskId)) {
            return 0;
        }
        //查询任务表task.json->task_sage_id
        $tInfo = model(TaskModel::class)
            ->field('JSON_UNQUOTE(JSON_EXTRACT(json,\'$.task_stage_id\')) as stage_id')
            ->where(['id' => $taskId])->find();

        return $tInfo['stage_id'] ?? 0;
    }


    /**
     * ---仅脚本使用---修补排期的数据
     * @param int $linkId
     * @return int
     */
    public function fixOriginalPlanNodePlanData(int $linkId)
    {
        $createTime = strtotime(date('Y-m-d') . ' 02:00:00');

        $data = model(PlanModel::class)
            ->where(['id' => $linkId, 'scope' => PlanModel::SCOPE_SINGLE])
            ->find();
        if (empty($data)) {
            return 0;
        }

        //原计划排期--类型
        $planMap = [PlanModel::TYPE_REVIEW, PlanModel::TYPE_MEETING];
        $type = in_array($data['type'], $planMap) ? OriginalPlanNodeModel::TYPE_STANDARD_PLAN : OriginalPlanNodeModel::TYPE_INNER_PLAN;

        //判断是否已有节点
        $orgModel = model(OriginalPlanNodeModel::class);
        $orgInfo = $orgModel->where(['link_id' => $data['id'], 'type' => $type])->find();
        if ($orgInfo) {
            return 0;
        }

        //获取阶段ID
        $data['stage_id'] = $this->getTaskStageId($data['task_id']);

        //查询环节ID
        $trtList = model(TaskRelationTagModel::class)
            ->where(['task_id' => $data['task_id'], 'link_module_code' => ['in', ['show_step_category', 'step_category']]])
            ->field('link_id')
            ->select();

        //循环小环节  条数不多
        foreach ($trtList as $val) {
            $data['step_category_id'] = $val['link_id'];
            //组装数据
            $modifyData = [
                'link_id' => $data['id'],
                'type' => $type,
                'tenant_id' => $data['tenant_id'] ?? 0,
                'episode_id' => $data['entity_id'] ?? 0,
                'step_category_id' => $data['step_category_id'] ?? 0,
                'delivery_time' => $data['end_time'],
                'name' => $data['name'] ?? '',
                'project_id' => $data['project_id'] ?? 0,
                'stage_id' => $data['stage_id'] ?? 0,
                'created_by' => $data['created_by'] ?? 0,
                'created' => $createTime,
            ];

            try {
                //查询 --- 所有实体、任务
                [$taskIds, $entityIds] = $this->getPlanAllTask($data);
                $modifyData['all_entity_ids'] = implode(',', $entityIds);
                $modifyData['all_task_ids'] = implode(',', $taskIds);
                //新增
                $nodeId = $orgModel->add($modifyData);
                //标准排期--根据任务清单--切割原计划节点
                OriginalPlanNodeService::getInstance()->splitStandardPlanData($nodeId);
                //根据任务工序ID 切割节点
                OriginalPlanNodeService::getInstance()->splitOriginalPlanNodeByStepId($nodeId);
            } catch (\Exception $e) {
                trace('更新排期节点失败' . PHP_EOL . format_exception_info($e), 'ERR');
            }
        }
        return 1;
    }


    /**
     * 需求单 -- 批量新增排期节点
     * @param int $linkId 需求单ID
     * @return int
     */
    public function addOriginalPlanNotes(int $linkId, int $crontab = 0)
    {
        $createTime = time();
        //脚本执行
        if ($crontab) {
            $createTime = strtotime(date('Y-m-d') . ' 02:00:00');
        }
        //判断 需求单
        $data = model(ReviewFeedbackModel::class)
            ->where(['id' => $linkId, 'status' => ReviewFeedbackModel::STATUS_FINAL])
            ->find();
        if (empty($data)) {
            return 0;
        }
        //补充租户ID
        $tenantId = request()->tenantId;
        $tenantId = $this->getProjectTenantId($tenantId, $data['project_id']);

        $name = $data['title'] ?? '';
        //原计划排期--类型
        $type = ($data['standard_append'] == 'yes') ? OriginalPlanNodeModel::TYPE_STANDARD_NOTE : OriginalPlanNodeModel::TYPE_NOTE;

        //判断是否已有节点
        $orgInfo = model(OriginalPlanNodeModel::class)->where(['link_id' => $data['id'], 'type' => $type])->find();
        if ($orgInfo) {
            return 0;
        }
        //交付时间
        $list = json_decode($data['json'], true);
        $list = $list['plan_delivery_time'] ?? [];

        //查询 --- 所有需求单实体、任务
        $allList = $this->getNoteAllTask($data['id'], $data['project_id']);

        $modifyData = [];
        foreach ($list as $episodeId => $val) {
            foreach ($list[$episodeId] as $step_category_id => $subVal) {
                //环节分组
                $categoryList = $allList[$step_category_id] ?? [];
                $modifyData[] = [
                    'episode_id' => $episodeId,
                    'step_category_id' => $step_category_id,
                    'delivery_time' => $subVal,
                    'type' => $type,
                    'name' => $name,
                    'all_entity_ids' => implode(',', array_unique(array_column($categoryList, 'entity_id'))),
                    'all_task_ids' => implode(',', array_unique(array_column($categoryList, 'id'))),
                    'link_id' => $data['id'],
                    'tenant_id' => $tenantId,
                    'project_id' => $data['project_id'] ?? 0,
                    'stage_id' => $data['stage_id'] ?? 0,
                    'created_by' => $data['created_by'] ?? 0,
                    'created' => $createTime,
                ];
            }
        }
        try {
            model(OriginalPlanNodeModel::class)->addAll($modifyData);
            //判断是否需要根据任务时间  切割成多个点
            $this->splitReviewFeedback($data['id']);
            //根据任务工序ID 切割节点
            OriginalPlanNodeService::getInstance()->splitOriginalPlanNodeByStepId(0, $data['id']);
            //更新详细表
            OriginalPlanNodeService::getInstance()->syncOriginalPlanNodeDetailByLinkId($data['id']);
        } catch (\Exception $e) {
            trace('更新排期失败' . PHP_EOL . format_exception_info($e), 'ERR');
        }
        return 1;
    }

    /**
     * 需求单 -- 获取所有任务、实体
     * @param int $reviewFeedbackId 需求单ID
     * @return array
     */
    private function getNoteAllTask(int $reviewFeedbackId, int $projectId)
    {
        //查询 --- 所有需求单实体
        $allEntityIds = model(ReviewFeedbackEntityModel::class)
            ->where([
                'review_feedback_id' => $reviewFeedbackId,
                'enable' => ['neq', ReviewFeedbackEntityModel::ENABLE_NO]
            ])->field('entity_id')->column('entity_id');
        if (empty($allEntityIds)) {
            return [];
        }

        //查询 --- 所有需求单  景响的任务
        $taskIdAffectecd = model(NoteEntityRelationModel::class)->alias('ner')
            ->join('note on note.id = ner.note_id')
            ->join('repair_order_affected_task roat on roat.task_repair_order_id = note.task_repair_order_id')
            ->where([
                'ner.review_feedback_id' => $reviewFeedbackId,
                'ner.relation_type' => 'step_category',
                'ner.entity_id' => ['in', $allEntityIds],
            ])
            ->field('roat.task_id')
            ->column('roat.task_id');
        //查询 --- 所有需求单实体的任务
        $taskIdEntity = model(TaskModel::class)
            ->where(['source_entity_id' => ['in', $allEntityIds]])
            ->where(['step_type' => 'step'])
            ->field('id')
            ->column('id');
        $taskIds = array_merge($taskIdEntity, $taskIdAffectecd);
        if (empty($taskIds)) {
            return [];
        }
        //任务根据  环节分组
        $allTaskEntity = model(TaskModel::class)
            ->where(['id' => ['in', $taskIds]])
            ->where(['step_type' => 'step', 'project_id' => $projectId])
            ->field('id,step_category_id,entity_id')
            ->select();
        $result = array_group_by($allTaskEntity, 'step_category_id');

        return $result;
    }

    /**
     * 保存排期节点--延期单
     * @param int $linkId 延期单ID
     * @return int
     */
    public function saveOriginalPlanNoteByDelayOrder(int $linkId, int $crontab = 0)
    {
        $createTime = time();
        //脚本执行
        if ($crontab) {
            $createTime = strtotime(date('Y-m-d') . ' 02:00:00');
        }
        //查询延期单
        $data = model(DelayOrderModel::class)
            ->where(['id' => $linkId, 'is_reviewed' => 'yes', 'confirm_status' => 'yes'])
            ->find();

        if (empty($data)) {
            return 0;
        }
        //判断是否已有节点
        $orgInfo = model(OriginalPlanNodeModel::class)->where(['link_id' => $data['id'], 'type' => ['in', [OriginalPlanNodeModel::TYPE_DELAY_ORDER, OriginalPlanNodeModel::TYPE_WORK_ORDER]]])->find();
        if ($orgInfo) {
            return 0;
        }

        //补充租户ID
        $tenantId = request()->tenantId;
        $tenantId = $this->getProjectTenantId($tenantId, $data['project_id']);

        //查询延期单任务--实体
        $dotTaskList = model(DelayOrderTaskModel::class)->alias('dot')
            ->join('task on task.id = dot.task_id', 'left')
            ->field('dot.delay_time,dot.plan_delivery_time, dot.type, dot.task_id, task.entity_id')
            ->where(['dot.delay_order_id' => $data['id']])
            ->select();
        //数组模拟group by分组
        $dotTaskList = arrayGroup($dotTaskList, 'delay_time', 'entity_id,task_id', 'type,plan_delivery_time');

        if (empty($dotTaskList)) {
            return 0;
        }
        //循环任务交付时间  一个时间点  一个节点
        $modifyData = [];
        foreach ($dotTaskList as $val) {

            //延期单类型---需求单｜工单｜延期单
            $type = OriginalPlanNodeModel::TYPE_DELAY_ORDER;
            if ($val['type'] == DelayOrderTaskModel::TYPE_WORK_ORDER) {
                $type = OriginalPlanNodeModel::TYPE_WORK_ORDER;
            }
            if ($val['type'] == DelayOrderTaskModel::TYPE_DEMAND_ORDER) {
                $type = OriginalPlanNodeModel::TYPE_NOTE;
            }

            //今日完成脚本
            if ($crontab && empty($val['delay_time'])) {
                $val['delay_time'] = strtotime(date('Y-m-d', $val['plan_delivery_time']) . ' 23:59:59');
            }
            if ($val['delay_time'] < $val['plan_delivery_time']) {
                $type = OriginalPlanNodeModel::TYPE_DELAY_EARLY;
            }

            $modifyData[] = [
                'type' => $type,
                'all_entity_ids' => $val['entity_id'],
                'all_task_ids' => $val['task_id'],
                'delivery_time' => $val['delay_time'],

                'link_id' => $data['id'],
                'tenant_id' => $tenantId,
                'episode_id' => $data['episode_id'] ?? 0,
                'step_category_id' => $data['step_category_id'] ?? 0,
                'name' => $data['name'] ?? '',
                'project_id' => $data['project_id'] ?? 0,
                'stage_id' => $data['stage_id'] ?? 0,
                'created_by' => $data['created_by'] ?? 0,
                'created' => $createTime,
            ];
        }

        try {
            model(OriginalPlanNodeModel::class)->addAll($modifyData);
            //根据任务工序ID 切割节点
            OriginalPlanNodeService::getInstance()->splitOriginalPlanNodeByStepId(0, $data['id']);
            //更新详细表
            OriginalPlanNodeService::getInstance()->syncOriginalPlanNodeDetailByLinkId($data['id']);
        } catch (\Exception $e) {
            trace('更新原计划节点--延期单失败' . PHP_EOL . format_exception_info($e), 'ERR');
        }
        return 1;
    }

    /**
     * 切割需求单的数据
     * @param int $linkId 需求单ID
     * @return int
     */
    private function splitReviewFeedback(int $linkId)
    {
        //需求单数据
        $condition = [];
        $condition['link_id'] = $linkId;
        $condition['type'] = ['in', [OriginalPlanNodeModel::TYPE_STANDARD_NOTE, OriginalPlanNodeModel::TYPE_NOTE]];

        //判断是否已有节点
        $list = model(OriginalPlanNodeModel::class)->where($condition)->select();
        if (empty($list)) {
            return 0;
        }
        //需求单
        $dotTaskIds = $this->getDelayOrderTask($linkId);

        //根据任务时间生成节点
        foreach ($list as $val) {
            $taskIds = explode(',', $val['all_task_ids']);
            $taskIds = array_diff($taskIds, $dotTaskIds);
            if (empty($taskIds)) {
                continue;
            }

            //根据任务时间生成节点
            $this->splitReviewFeedbackData($taskIds, $val);
        }
        return 1;
    }

    /**
     * 根据任务时间生成节点
     */
    public function splitReviewFeedbackData($taskIds, $oInfo)
    {
        $createTime = strtotime(date('Y-m-d') . ' 02:00:00');
        //任务根据  环节分组
        $list = model(TaskModel::class)
            ->where(['id' => ['in', $taskIds]])
            ->where(['step_type' => 'step'])
            ->field("id as tid, entity_id, plan_delivery_time")
            ->select();

        //数组模拟group by分组
        $list = arrayGroup($list, 'plan_delivery_time', 'entity_id,tid');
        if (count($list) == 1) {
            //如果没变化不修改
            if ($list[0]['plan_delivery_time'] == $oInfo['delivery_time']) {
                return 2;
            }
        }

        $orgModel = model(OriginalPlanNodeModel::class);
        foreach ($list as $val) {
            $modifyData = [
                'all_task_ids' => $val['tid'],
                'all_entity_ids' => $val['entity_id'],
                'delivery_time' => $val['plan_delivery_time'],
                'episode_id' => $oInfo['episode_id'],
                'step_category_id' => $oInfo['step_category_id'],
                'type' => $oInfo['type'],
                'name' => $oInfo['name'],
                'link_id' => $oInfo['link_id'],
                'tenant_id' => $oInfo['tenant_id'],
                'project_id' => $oInfo['project_id'],
                'stage_id' => $oInfo['stage_id']
            ];
            $orgInfo = $orgModel->where($modifyData)->find();
            if (!$orgInfo) {
                $modifyData['created_by'] = $oInfo['created_by'];
                $modifyData['created'] = $createTime;
                //脚本使用--不用additem--自动生成创建信息
                $orgModel->add($modifyData);
            }
        }
        try {
            model(OriginalPlanNodeModel::class)->where(['id' => $oInfo['id']])->delete();
        } catch (\Exception $e) {
            trace('更新排期节点失败' . PHP_EOL . format_exception_info($e), 'ERR');
        }

        return $list;
    }

    /**
     * 需求单--获取延期单任务ID
     */
    private function getDelayOrderTask(int $reviewFeedbackId)
    {
        if (empty($reviewFeedbackId)) {
            return [];
        }
        //任务完成状态
        $statusDone = StatusService::getInstance()->getTaskDoneStatus();

        //查询条件
        $where = [];
        $where['link_id'] = $reviewFeedbackId;
        $where['type'] = DelayOrderTaskModel::TYPE_DEMAND_ORDER;

        $where['task_status_id'] = ['NEQ', $statusDone['id']];
        $where['_string'] = 'delay_time != plan_delivery_time';

        $dotTaskIds = model(DelayOrderTaskModel::class)
            ->where($where)
            ->field('task_id')
            ->column('task_id');
        if (empty($dotTaskIds)) {
            $dotTaskIds = [];
        }

        return $dotTaskIds;
    }


    /**
     * 获取项目租户ID--脚本执行时（无租户ID）
     * @param int $tenantId 租户ID
     * @param int $projectId 项目ID
     * @return int|mixed
     */
    private function getProjectTenantId(int $tenantId, int $projectId)
    {
        if ($tenantId) {
            return $tenantId;
        }
        $projectInfo = model(ProjectModel::class)->field('tenant_id')->find($projectId);

        return $projectInfo['tenant_id'] ?? 0;
    }


    /**
     * 切割--标准排期数据
     * @param int $nodeId 节点ID
     * @return int
     */
    public function splitStandardPlanData(int $nodeId)
    {
        $oInfo = model(OriginalPlanNodeModel::class)->where(['id' => $nodeId, 'type' => OriginalPlanNodeModel::TYPE_STANDARD_PLAN])->find();
        if (empty($oInfo)) {
            return 1;
        }
        $taskIds = explode(',', $oInfo['all_task_ids']);
        if (count($taskIds) <= 1) {
            return 1;
        }
        //查询环节ID
        $trtList = model(TaskRelationTagModel::class)
            ->where(['task_id' => ['in', $taskIds], 'link_module_code' => ['in', ['show_step_category', 'step_category']]])
            ->field('link_id,task_id')
            ->select();

        //以link_id为键--生成数组
        $trtList = arrayGroup($trtList, 'link_id', 'task_id');
        //循环环节--生成节点
        foreach ($trtList as $val) {
            //根据任务ID  时间--生成节点
            $taskIds = explode(',', $val['task_id']);
            $this->saveStandardPlanData($taskIds, $oInfo, $val['link_id']);
        }

        return 1;
    }


    /**
     * 同步标准排期--节点数据
     * @param array $taskIds 任务ID
     * @param array $oInfo 节点信息
     * @param int   $stepCategoryId 环节ID
     * @return int
     */
    private function saveStandardPlanData(array $taskIds, array $oInfo, int $stepCategoryId)
    {
        if (empty($taskIds)) {
            return 0;
        }
        //任务清单ids条数不多
        $list = model(TaskModel::class)
            ->where(['id' => ['in', $taskIds]])
            ->field("id as tid, entity_id, plan_delivery_time")
            ->select();

        //数组模拟group by分组
        $list = arrayGroup($list, 'plan_delivery_time', 'entity_id,tid');

        //先删除--后新增
        model(OriginalPlanNodeModel::class)->where(['id' => $oInfo['id']])->delete();

        //新增节点
        $createTime = time();
        $orgModel = model(OriginalPlanNodeModel::class);
        foreach ($list as $val) {
            $modifyData = [
                'all_task_ids' => $val['tid'],
                'all_entity_ids' => $val['entity_id'],
                'delivery_time' => $val['plan_delivery_time'],
                'episode_id' => $oInfo['episode_id'],
                'step_category_id' => $stepCategoryId,
                'type' => $oInfo['type'],
                'name' => $oInfo['name'],
                'link_id' => $oInfo['link_id'],
                'tenant_id' => $oInfo['tenant_id'],
                'project_id' => $oInfo['project_id'],
                'stage' => $oInfo['stage'],
                'stage_id' => $oInfo['stage_id']
            ];
            $orgInfo = $orgModel->where($modifyData)->find();
            if (!$orgInfo) {
                $modifyData['created_by'] = $oInfo['created_by'];
                $modifyData['created'] = $createTime;
                //脚本使用--不用additem--自动生成创建信息
                $orgModel->add($modifyData);
            }
        }
        return 1;
    }

    /**
     * 获取任务异常信息
     * @param int $taskId 任务ID
     * @param int $nodeId 节点ID
     * @return array|int
     */
    public function getTaskAbnormalInfo(int $taskId, int $nodeId, int $nodeType)
    {
        //任务状态
        $statusDone = StatusService::getInstance()->getTaskDoneStatus();
        $closedStatus = StatusService::getInstance()->getTaskDefaultClosedStatus();

        //任务详情
        $taskInfo = model(TaskModel::class)
            ->where(['id' => $taskId])
            ->where(["json->>'$.task_status'" => ['NEQ', $closedStatus['id']]])
            ->field("id,step_id,step_category_id,entity_id,project_id,json->>'$.task_status' task_status,end_time,plan_delivery_time")
            ->find();
        //任务已取消
        if (empty($taskInfo)) {
            return 0;
        }
        //任务--无异常：完成时间 小于 计划交付时间
        if ($taskInfo['task_status'] == $statusDone && $taskInfo['end_time'] <= $taskInfo['plan_delivery_time']) {
            return 0;
        }

        //查询任务--延期情况
        [$abnormalType, $delayInfo] = $this->getTaskDelayInfo($taskId);

        //查询节点是否被更改--需求单、排期
        [$abnormalTypeNode, $abnormalNodeList] = $this->getAbnormalNodeInfo($taskId, $nodeId);
        if ($abnormalTypeNode) {
            $abnormalType = $abnormalTypeNode;
        }

        //交付节点 --- 查询上游异常
        if ($nodeType == OriginalPlanNodeModel::NODE_TYPE_DELIVERY && empty($delayInfo) && empty($abnormalNodeList)) {
            [$abnormalTypeUpstream, $taskUpstreamInfo] = $this->getTaskUpstreamAbnormal($taskInfo['id'], $taskInfo['project_id'], $taskInfo['step_id']);
            if ($abnormalTypeUpstream) {
                $abnormalType = $abnormalTypeUpstream;
            }
        }

        //7=未处理（逾期）
        if (empty($abnormalType) && empty($abnormalTypeNode) && empty($abnormalTypeUpstream)) {
            $abnormalType = OriginalPlanNodeDailyReportModel::ABNORMAL_TYPE_NOT_DONE;
        }

        $desc = [];
        $desc['delay_list'] = $delayInfo;
        $desc['entity_id'] = $taskInfo['entity_id'];
        $desc['step_category_id'] = $taskInfo['step_category_id'];
        $desc['abnormal_node_list'] = $abnormalNodeList;
        $desc['upstream_task_list'] = $taskUpstreamInfo ?? [];

        return [
            'abnormal_type' => $abnormalType,
            'desc' => $desc,
        ];
    }

    /**
     * 获取任务延期信息
     * @param int $taskId 任务ID
     * @return array
     */
    private function getTaskDelayInfo(int $taskId)
    {
        //判断是否延期过
        $dotInfo = model(DelayOrderTaskModel::class)
            ->field('id,task_id,type,delay_reason,delay_reason_type')
            ->where(['task_id' => $taskId])
            ->order('id desc')
            ->find();

        $abnormalType = 0;
        //存在返回
        if ($dotInfo) {
            switch ($dotInfo['type']) {
                //需求变更
                case DelayOrderTaskModel::TYPE_DEMAND_ORDER:
                    $abnormalType = OriginalPlanNodeDailyReportModel::ABNORMAL_TYPE_NODE;
                    //需求变更
                    break;
                case DelayOrderTaskModel::TYPE_WORK_ORDER:
                    $abnormalType = OriginalPlanNodeDailyReportModel::ABNORMAL_TYPE_WORK_ORDER;
                    //工单返修
                    break;
                default:
                    $abnormalType = OriginalPlanNodeDailyReportModel::ABNORMAL_TYPE_DELAY;
                    //申请延期
                    break;
            }
        }
        return [$abnormalType, $dotInfo];
    }

    /**
     * 获取上一个节点信息
     * @param int $taskId 任务ID
     * @param int $nodeId 节点ID
     * @return array
     */
    public function getAbnormalNodeInfo(int $taskId, int $nodeId)
    {
        //查询节点是否被更改--需求单、排期----晚上脚本执行
        $lastNodeList = model(OriginalPlanNodeModel::class)
            ->where("find_in_set({$taskId}, all_task_ids)")
            ->where(['id' => ['gt', $nodeId], 'type' => ['lt', OriginalPlanNodeModel::TYPE_DELAY_ORDER]])
            ->field('id,link_id,type')
            ->select();

        $abnormalType = 0;
        if ($lastNodeList) {
            $lastNodeInfo = end($lastNodeList);
            //排期--更改--任务节点
            if (in_array($lastNodeInfo['type'], [OriginalPlanNodeModel::TYPE_STANDARD_PLAN, OriginalPlanNodeModel::TYPE_INNER_PLAN])) {
                $abnormalType = OriginalPlanNodeDailyReportModel::ABNORMAL_TYPE_PLAN;
            }
            //需求单--修改-任务节点
            if (in_array($lastNodeInfo['type'], [OriginalPlanNodeModel::TYPE_STANDARD_NOTE, OriginalPlanNodeModel::TYPE_NOTE])) {
                $abnormalType = OriginalPlanNodeDailyReportModel::ABNORMAL_TYPE_NODE;
            }
        }

        return [$abnormalType, $lastNodeList];
    }

    /**
     * 查询上游任务异常
     * @param int $taskId 任务ID
     * @param int $projectId 项目ID
     * @param int $step_id 工序ID
     * @return array|int
     */
    private function getTaskUpstreamAbnormal(int $taskId, int $projectId, int $step_id)
    {
        //上游交付工序
        $upStepIds = $this->getProjectStepUpstream($projectId, $step_id);

        //下游任务IDs
        $taskUpstreamIds = model(TaskRelationModel::class)
            ->setSuffix('_project_' . $projectId)
            ->where(['relation_type' => ['in', TaskRelationModel::RELATION_TYPE_LIST], 'target_task_id' => $taskId])
            ->field('source_task_id')
            ->column('source_task_id');
        if (empty($taskUpstreamIds) || empty($upStepIds)) {
            return 0;
        }

        //上游任务--是否有--未完成
        $statusDone = StatusService::getInstance()->getTaskDoneStatus();
        $taskUpstreamInfo = model(TaskModel::class)
            ->where(["json->>'$.task_status'" => ['NEQ', $statusDone['id']]])
            ->where(['id' => ['in', $taskUpstreamIds], 'step_id' => ['in', $upStepIds]])
            ->field('id')
            ->select();
        if (empty($taskUpstreamInfo)) {
            return 0;
        }

        //上游未交付且未解决
        $abnormalType = OriginalPlanNodeDailyReportModel::ABNORMAL_TYPE_UPSTREAM_NOT_DONE;

        return [$abnormalType, $taskUpstreamInfo];
    }

    /**
     * 查询上游交付工序
     * @param int $projectId 项目ID
     * @param int $stepId 工序ID
     * @return array
     */
    private function getProjectStepUpstream(int $projectId, int $stepId)
    {
        $projectTemplateId = model(ProjectModel::class)
            ->where(['id' => $projectId])
            ->cache(C('database.default_query_cache_time'))
            ->value('project_template_id');

        //查询条件
        $where = [];
        $where['project_template_id'] = $projectTemplateId;
        if ($stepId) {
            $where["step_id"] = $stepId;
        }
        //返回上游工序
        return model(StepUpstreamModel::class)
            ->field("id,config->>'$.step_id' up_step_id")
            ->where($where)
            ->cache(C('database.default_query_cache_time'))
            ->column('up_step_id');
    }

    /**
     * 查询下游交付工序
     * @param int $projectId 项目ID
     * @param int $stepId 工序ID
     * @return array
     */
    private function getProjectStepDownstream(int $projectId, int $stepId = 0)
    {
        $projectTemplateId = model(ProjectModel::class)
            ->where(['id' => $projectId])
            ->cache(C('database.default_query_cache_time'))
            ->value('project_template_id');

        //查询条件
        $where = [];
        $where['project_template_id'] = $projectTemplateId;
        //交付工序
        if ($stepId) {
            $where["config->>'$.step_id'"] = $stepId;
        }
        //返回下游工序
        return model(StepUpstreamModel::class)
            ->field("id,step_id")
            ->where($where)
            ->cache(C('database.default_query_cache_time'))
            ->column('step_id');
    }

    /**
     * 通过任务ID查询目标环节
     * @param int $projectId 项目ID
     * @param int $taskId 任务ID
     * @return string
     */
    public function getTargetStepCategory(int $projectId, int $taskId)
    {
        $where = [];
        $where['source_task_id'] = $taskId;
//        $where['_string'] = "target_step_category_id != source_step_category_id";
        //上游 -- 实体 -- 关联
        $where['relation_type'] = ['in', TaskRelationModel::RELATION_TYPE_LIST];

        //交付的任务列表 -- 查询 task_relation
        $list = model(TaskRelationModel::class)->setSuffix('_project_' . $projectId)->where($where)->field('DISTINCT target_step_category_id')->select();

        $targetCategoryIds = array_column($list, 'target_step_category_id');

        return implode(',', array_unique($targetCategoryIds));
    }

    /**
     * 根据任务工序ID切割节点
     * @param int $nodeId 节点ID
     * @param int $linkId linkID
     * @return int
     */
    public function splitOriginalPlanNodeByStepId(int $nodeId = 0, int $linkId = 0)
    {
        //查询工序ID为0的节点
        $where = [];
        $where['step_id'] = 0;
        if ($nodeId) {
            $where['id'] = $nodeId;
        }
        if ($linkId) {
            $where['link_id'] = $linkId;
        }
        if (empty($nodeId) && empty($linkId)) {
            return 1;
        }
        //查询记录
        $list = model(OriginalPlanNodeModel::class)->where($where)->select();
        if (empty($list)) {
            return 1;
        }
        foreach ($list as $val) {
            //全量任务
            $allTaskIds = explode(',', $val['all_task_ids']);
            //没任务--跳出循环
            if (empty($allTaskIds)) {
                continue;
            }
            $this->saveOriginalPlanNodeByStepId($allTaskIds, $val);
        }
        return 1;
    }

    /**
     * 保存--根据工序ID切割节点数据
     * @param array $allTaskIds 所有任务
     * @param array $info 详情
     * @return int
     */
    private function saveOriginalPlanNodeByStepId(array $allTaskIds, array $info)
    {
        $orgNodeModel = model(OriginalPlanNodeModel::class);
        //查询任务--的工序
        $taskList = model(TaskModel::class)->where(['id' => ['in', $allTaskIds]])->field('id,step_id,entity_id')->select();
        $stepTaskList = arrayGroup($taskList, 'step_id', 'id,entity_id');
        try {
            $i = 0;
            //任务的工序列表不多
            foreach ($stepTaskList as $stepVal) {
                //工序ID
                $stepId = $stepVal['step_id'];
                if (empty($stepId)) {
                    continue;
                }
                //查询交付环节
                $taskIdList = explode(',', $stepVal['id']);
                $taskId = $taskIdList[0] ?? 0;
                $targetStepCategoryIds = $this->getTargetStepCategory($info['project_id'], $taskId);
                //节点类型--有交付环节==交付节点---
                $nodeType = 1;
                if ($targetStepCategoryIds) {
                    $nodeType = OriginalPlanNodeModel::NODE_TYPE_DELIVERY;
                }
                //第一次更新
                if (empty($i)) {
                    //有更新
                    $modifyData = [
                        'all_task_ids' => $stepVal['id'],
                        'all_entity_ids' => $stepVal['entity_id'],
                        'node_type' => $nodeType,
                        'target_step_category_ids' => $targetStepCategoryIds,
                        'step_id' => $stepId
                    ];
                    $orgNodeModel->where(['id' => $info['id']])->save($modifyData);
                } else {
                    $addData = $info;
                    //删除ID -- 重新新增
                    unset($addData['id']);
                    $addData['all_task_ids'] = $stepVal['id'];
                    $addData['all_entity_ids'] = $stepVal['entity_id'];
                    $addData['step_id'] = $stepId;
                    $addData['node_type'] = $nodeType;
                    $addData['target_step_category_ids'] = $targetStepCategoryIds;
                    //查询--去重复
                    $hasOne = $orgNodeModel->where($addData)->find();
                    if (empty($hasOne)) {
                        $orgNodeModel->add($addData);
                    }
                }
                $i++;
            }
        } catch (\Exception $e) {
            trace('工序ID拆分节点失败' . PHP_EOL . format_exception_info($e), 'ERR');
        }
        return 1;
    }


    /**
     * 通过link_id--同步--原计划节点详细表
     * @param int $linkId 关联ID  link_id
     * @return void
     */
    public function syncOriginalPlanNodeDetailByLinkId(int $linkId)
    {
        $condition = [];
        $condition['link_id'] = $linkId;
        //查询link_id 关联的节点
        $list = model(OriginalPlanNodeModel::class)
            ->where($condition)
            ->field("id")
            ->select();

        foreach ($list as $val) {
            $this->syncOriginalPlanNodeDetailById($val['id']);
        }
    }

    /**
     * 根据节点ID--同步--原计划节点详细表
     * @param int $originalPlanNodeId 原计划节点ID
     * @return string
     */
    public function syncOriginalPlanNodeDetailById(int $originalPlanNodeId)
    {
        //查询原计划节点信息
        $info = model(OriginalPlanNodeModel::class)
            ->where(['id' => $originalPlanNodeId, 'is_sync_detail' => OriginalPlanNodeModel::IS_SYNC_DETAIL_NO])
            ->field("id,all_task_ids,delivery_task_ids,project_id,delivery_time,type,link_id,node_type,step_category_id,created")
            ->find();
        $allTaskIdList = explode(',', $info['all_task_ids']);
        $deliveryTaskIdList = explode(',', $info['delivery_task_ids']);
        if (empty($allTaskIdList)) {
            return 'OK';
        }

        //判断节点记录是否--已存在详细记录
        $has = model(OriginalPlanNodeDetailModel::class)
            ->where(['original_plan_node_id' => $originalPlanNodeId])
            ->field('id')
            ->find();
        if (!empty($has)) {
            //原计划节点更新为 --已同步
            $this->updateOriginalNodeIsSync($originalPlanNodeId);

            return 'OK';
        }

        try {
            //批量获取实体信息
            $entityList = $this->batchGetTaskEntityList($allTaskIdList);
            //获取标准任务的剧集--单独获取
            $standardEpisodeList = $this->getStandardTaskEpisode($entityList);

            //先更新---更新已有记录--交付状态
            $this->updateNodeDetailByTaskIds($allTaskIdList, $info['delivery_time'], $info['link_id'], $info['type']);
            //批量插入记录
            $this->batchInsertNodeDetail($allTaskIdList, $entityList, $info, $deliveryTaskIdList, $standardEpisodeList);

            //原计划节点更新为 --已同步
            $this->updateOriginalNodeIsSync($originalPlanNodeId);

        } catch (\Exception $e) {
            trace('同步原计划节点详细失败' . PHP_EOL . format_exception_info($e), 'ERR');
        }
        return 'OK';
    }

    /**
     * 原计划节点更新为 --已同步
     * @return void
     */
    private function updateOriginalNodeIsSync($id){
        model(OriginalPlanNodeModel::class)->where(['id' => $id])->save(['is_sync_detail' => OriginalPlanNodeModel::IS_SYNC_DETAIL_YES]);
    }

    /**
     * 批量更新--已有记录
     * @param array $taskIdList 任务ID列表
     * @param int   $newDeliveryTime 新交付时间
     * @param int   $closeLinkId 异常关闭link_id
     * @param int   $nodeType 节点类型
     * @return void
     */
    private function updateNodeDetailByTaskIds(array $taskIdList, int $newDeliveryTime, int $closeLinkId, int $nodeType)
    {
        //原计划节点类型 和 节点详情类型 对映map
        $typeMap = [
            OriginalPlanNodeModel::TYPE_STANDARD_PLAN => OriginalPlanNodeDetailModel::CLOSE_LINK_TYPE_PLAN,
            OriginalPlanNodeModel::TYPE_INNER_PLAN => OriginalPlanNodeDetailModel::CLOSE_LINK_TYPE_PLAN,
            OriginalPlanNodeModel::TYPE_STANDARD_NOTE => OriginalPlanNodeDetailModel::CLOSE_LINK_TYPE_NOTE,
            OriginalPlanNodeModel::TYPE_NOTE => OriginalPlanNodeDetailModel::CLOSE_LINK_TYPE_NOTE,
            OriginalPlanNodeModel::TYPE_DELAY_ORDER => OriginalPlanNodeDetailModel::CLOSE_LINK_TYPE_DELAY,
            OriginalPlanNodeModel::TYPE_WORK_ORDER => OriginalPlanNodeDetailModel::CLOSE_LINK_TYPE_WORK_ORDER,
            OriginalPlanNodeModel::TYPE_DELAY_EARLY => OriginalPlanNodeDetailModel::CLOSE_LINK_TYPE_DELAY,
        ];

        $orgNodeDetailModel = model(OriginalPlanNodeDetailModel::class);
        //分片批量更新
        foreach (array_chunk($taskIdList, 50) as $taskIds) {
            if (empty($taskIds)) {
                continue;
            }
            //更新（节点详细表）任务（交付状态= 为默认）的数据
            $condition = [
                'task_id' => ['in', $taskIds],
                'delivery_status' => OriginalPlanNodeDetailModel::DELIVERY_STATUS_DEFAULT,
            ];
            //需判断记录是否存在
            $has = $orgNodeDetailModel->where($condition)->field('id')->find();
            if (empty($has)) {
                continue;
            }
            //更新记录
            $orgNodeDetailModel->where($condition)->save([
                //交付状态
                'delivery_status' => OriginalPlanNodeDetailModel::DELIVERY_STATUS_UNDELIVERED,
                //新延期交付时间
                'new_delivery_time' => $newDeliveryTime,
                //异常结束 来源ID
                'close_link_id' => $closeLinkId,
                //异常结束类型
                'close_link_type' => $typeMap[$nodeType] ?? OriginalPlanNodeDetailModel::CLOSE_LINK_TYPE_DEFAULT,
            ]);
        }
    }


    /**
     * 批量插入记录
     * @param array $taskIdList 任务ID列表
     * @param array $taskEntityList 实体信息
     * @param array $info 原节点信息
     * @return void
     */
    private function batchInsertNodeDetail(array $taskIdList, array $taskEntityList, array $info, array $deliveryTaskIdList, array $standardEpisodeList)
    {
        $orgNodeDetailModel = model(OriginalPlanNodeDetailModel::class);
        //实体信息
        $taskEntityList = array_column($taskEntityList, null, 'task_id');
        //分片批量更新
        foreach (array_chunk($taskIdList, 50) as $taskIds) {
            if (empty($taskIds)) {
                return;
            }
            $insertData = [];
            foreach ($taskIds as $taskId) {
                $taskEntityInfo = $taskEntityList[$taskId] ?? [];
                $standardTaskEntityInfo = $standardEpisodeList[$taskId] ?? [];
                if (empty($taskEntityInfo)) {
                    continue;
                }
                //组装节点详细--数据
                $detailData = [
                    'project_id' => $info['project_id'],
                    'task_id' => $taskId,
                    'original_plan_node_id' => $info['id'],
                    'original_delivery_time' => $info['delivery_time'],
                    'created' => $info['created'],
                    'node_type' => $info['node_type'],
                    'entity_id' => $taskEntityInfo['entity_id'],
                    'step_category_id' => empty($taskEntityInfo['step_category_id']) ? $info['step_category_id'] : $taskEntityInfo['step_category_id'],
                    //实体不存在时  module_id为null
                    'entity_module_id' => empty($taskEntityInfo['module_id']) ? $standardTaskEntityInfo['module_id'] ?? 0 : $taskEntityInfo['module_id'],
                    'initial_episode_code' => empty($taskEntityInfo['initial_episode_code']) ? $standardTaskEntityInfo['initial_episode_code'] ?? '' : $taskEntityInfo['initial_episode_code'],
                    'showings_number' => empty($taskEntityInfo['showings_number']) ? $standardTaskEntityInfo['showings_number'] ?? '' : $taskEntityInfo['showings_number'],
                    'step_type' => $taskEntityInfo['step_type'] ?? 'step',
                    //交付状态  判断是否已交付
                    'delivery_status' => in_array($taskId, $deliveryTaskIdList) ? OriginalPlanNodeDetailModel::DELIVERY_STATUS_DELIVERED : OriginalPlanNodeDetailModel::DELIVERY_STATUS_DEFAULT,
                ];
                $insertData[] = $detailData;
            }
            $orgNodeDetailModel->addAll($insertData);
        }
    }


    /**
     * 批量获取任务的实体信息
     * @param array $taskIdList
     * @return array
     */
    private function batchGetTaskEntityList(array $taskIdList)
    {
        if (empty($taskIdList)) {
            return [];
        }
        $entityTaskList = [];
        //分片批量获取实体信息
        foreach (array_chunk($taskIdList, 50) as $taskIds) {
            $tempList = model(TaskModel::class)
                ->field([
                    'entity.module_id,entity.showings_number,entity.initial_episode_code',
                    'task.step_category_id,task.id as task_id,task.entity_id,task.step_type',
                ])
                ->join('entity on entity.id = task.entity_id', 'LEFT')
                ->where(['task.id' => ['IN', $taskIds]])
                ->select();

            $entityTaskList = array_merge($entityTaskList, $tempList);
        }
        return $entityTaskList;
    }

    /**
     * 标准任务的剧集--单独获取
     * @param array $entityList
     * @return array
     */
    private function getStandardTaskEpisode(array $entityList)
    {
        $standardTaskIds = [];
        foreach ($entityList as $val) {
            if ($val['step_type'] != TaskModel::STEP_TYPE_STEP) {
                $standardTaskIds[] = $val['task_id'];
            }
        }
        if (empty($standardTaskIds)) {
            return [];
        }
        $trtList = model(TaskRelationTagModel::class)->alias('trt')
            ->field('entity.module_id,entity.showings_number,entity.initial_episode_code,trt.task_id')
            ->join('entity on entity.id = trt.link_id', 'LEFT')
            ->where(['trt.task_id' => ['in', $standardTaskIds], 'trt.link_module_code' => 'episode'])
            ->select();
        return array_column($trtList, null, 'task_id');
    }

    /**
     * 更新详细节点表  --  关闭类型
     * @param int $taskId  任务ID
     * @return void
     */
    public function updateNodeDetail(int $taskId)
    {
        $orgNodeDetailModel = model(OriginalPlanNodeDetailModel::class);
        //更新--否存在过去时间内未关闭的交付节点
        $now = time();
        $condition = [
            'task_id' => $taskId,
            //交付状态
            'delivery_status' => OriginalPlanNodeDetailModel::DELIVERY_STATUS_DEFAULT,
        ];
        //需判断记录是否存在
        $info = $orgNodeDetailModel->where($condition)->field('id,original_delivery_time')->find();
        if (empty($info)) {
            return;
        }

        //
        $udata = [];
        if ( $info['original_delivery_time']> $now) {
            $udata['delivery_status'] = OriginalPlanNodeDetailModel::DELIVERY_STATUS_DELIVERED;
        }  else {
            //查询任务 -- 交付时间
            $tInfo = model(TaskModel::class)->field('delivery_time')->where(['id' => $taskId])->find();
            //更新交付状态为未交付，实际交付时间为当前任务实际交付时间，关闭类型为7 逾期交付
            $udata['delivery_status'] = OriginalPlanNodeDetailModel::DELIVERY_STATUS_UNDELIVERED;
            $udata['new_delivery_time'] = $tInfo['delivery_time'] ?? 0;
            $udata['close_link_type'] = OriginalPlanNodeDetailModel::CLOSE_LINK_LATE_DELIVERED;
        }
        //更新
        $orgNodeDetailModel->where($condition)->save($udata);
    }

}
